﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="MatrixExtensions.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra.Sequential.DoubleComplexExtensions
{
    #region Usings

    using System;
    using System.Diagnostics.CodeAnalysis;

    using ExulLibrary.Mathematics.LinearAlgebra.Exceptions;

    #endregion

    /// <summary>
    ///     Provides extension methods for <see cref="Matrix{T}"/> with <see cref="DoubleComplex"/> type parameter.
    /// </summary>
    public static class MatrixExtensions
    {
        #region Fields

        #region private

        /// <summary>
        ///     The collection of providers for multiplication operation.
        /// </summary>
        private static IMultiplicableMatrixOperationsProvider<DoubleComplex>[] MultiplicationOperationsProviders =
            new IMultiplicableMatrixOperationsProvider<DoubleComplex>[0];

        /// <summary>
        ///     The collection of providers for addition/multiplication of matrix with/by factor.
        /// </summary>
        private static IMatrixFactorRingOperationsProvider<DoubleComplex>[] MatrixFactorRingOperationsProviders =
            new IMatrixFactorRingOperationsProvider<DoubleComplex>[0];

        /// <summary>
        ///     The collection of providers for addition/subtraction of matrices.
        /// </summary>
        private static IMatrixMatrixRingOperationsProvider<DoubleComplex>[][] MatrixMatrixRingOperationsProviders =
            new IMatrixMatrixRingOperationsProvider<DoubleComplex>[0][];

        #endregion

        #endregion

        #region Constructors and Destructors

        #region ...

        /// <summary>
        ///     Initializes static members of the <see cref="MatrixExtensions"/> class.
        /// </summary>
        [SuppressMessage(
            "Microsoft.Maintainability",
            "CA1506:AvoidExcessiveClassCoupling",
            Justification = "This class uses internal providers to hide them from external code.")]
        [SuppressMessage(
            "Microsoft.Performance",
            "CA1810:InitializeReferenceTypeStaticFieldsInline",
            Justification = "Initialization is too complex.")]
        static MatrixExtensions()
        {
            RegisterOperationsProvider(
                typeof(CcsSparseMatrix<DoubleComplex>), new CcsSparseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrsSparseMatrix<DoubleComplex>), new CrsSparseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrcsSparseMatrix<DoubleComplex>), new CrcsSparseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrcsSparseSymmetricMatrix<DoubleComplex>),
                new CrcsSparseSymmetricMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(typeof(RMDenseMatrix<DoubleComplex>), new RMDenseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(RMFlattenDenseMatrix<DoubleComplex>), new RMFlattenDenseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(typeof(CMDenseMatrix<DoubleComplex>), new CMDenseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMFlattenDenseMatrix<DoubleComplex>), new CMFlattenDenseMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(DenseSymmetricMatrix<DoubleComplex>), new DenseSymmetricMatrixMultiplicationOperationsProvider());
            RegisterOperationsProvider(
                typeof(DiagonalMatrix<DoubleComplex>), new DiagonalMatrixMultiplicationOperationsProvider());

            RegisterOperationsProvider(typeof(RMDenseMatrix<DoubleComplex>), new RMDenseMatrixFactorRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(RMFlattenDenseMatrix<DoubleComplex>), new RMFlattenDenseMatrixFactorRingOperationsProvider());
            RegisterOperationsProvider(typeof(CMDenseMatrix<DoubleComplex>), new CMDenseMatrixFactorRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMFlattenDenseMatrix<DoubleComplex>), new CMFlattenDenseMatrixFactorRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(DenseSymmetricMatrix<DoubleComplex>), new DenseSymmetricMatrixFactorRingOperationsProvider());
            RegisterOperationsProvider(typeof(DiagonalMatrix<DoubleComplex>), new DiagonalMatrixFactorRingOperationsProvider());

            RegisterOperationsProvider(
                typeof(RMDenseMatrix<DoubleComplex>),
                typeof(RMDenseMatrix<DoubleComplex>),
                new RMDenseMatrixRMDenseMatrixRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(RMFlattenDenseMatrix<DoubleComplex>),
                typeof(RMFlattenDenseMatrix<DoubleComplex>),
                new RMFlattenDenseMatrixRMFlattenDenseMatrixRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMDenseMatrix<DoubleComplex>),
                typeof(CMDenseMatrix<DoubleComplex>),
                new CMDenseMatrixCMDenseMatrixRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMFlattenDenseMatrix<DoubleComplex>),
                typeof(CMFlattenDenseMatrix<DoubleComplex>),
                new CMFlattenDenseMatrixCMFlattenDenseMatrixRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(DenseSymmetricMatrix<DoubleComplex>),
                typeof(DenseSymmetricMatrix<DoubleComplex>),
                new DenseSymmetricMatrixDenseSymmetricMatrixRingOperationsProvider());
            RegisterOperationsProvider(
                typeof(DiagonalMatrix<DoubleComplex>),
                typeof(DiagonalMatrix<DoubleComplex>),
                new DiagonalMatrixDiagonalMatrixRingOperationsProvider());
        }

        #endregion

        #endregion

        #region Methods

        #region public

        /// <summary>
        ///     Adds the specified factor to all elements of the matrix.
        /// </summary>
        /// <param name="matrix">
        ///     The matrix whose elements should be added.
        /// </param>
        /// <param name="factor">
        ///     The factor to add.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Add(this Matrix<DoubleComplex> matrix, DoubleComplex factor)
        {
#if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

            if ((MatrixFactorRingOperationsProviders.Length <= matrix.MatrixRegistrationIndex) ||
                (MatrixFactorRingOperationsProviders[matrix.MatrixRegistrationIndex] == null))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixFactorRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixFactorRingOperationsProviders[matrix.MatrixRegistrationIndex].Add(matrix, factor);
        }

        /// <summary>
        ///     Adds the second matrix to the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Add(this Matrix<DoubleComplex> augend, Matrix<DoubleComplex> addend)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= augend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex].Length <= addend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex][addend.MatrixRegistrationIndex].Add(
                augend, addend);
        }

        /// <summary>
        ///     Adds the second matrix to the first matrix multiplied by the factor.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendFactor">
        ///     The factor of the augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Add(this Matrix<DoubleComplex> augend, DoubleComplex augendFactor, Matrix<DoubleComplex> addend)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= augend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex].Length <= addend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex][addend.MatrixRegistrationIndex].Add(
                augend, augendFactor, addend);
        }

        /// <summary>
        ///     Adds the second matrix multiplied by the factor to the first matrix.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <param name="addendFactor">
        ///     The factor of the addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Add(this Matrix<DoubleComplex> augend, Matrix<DoubleComplex> addend, DoubleComplex addendFactor)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= augend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex].Length <= addend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex][addend.MatrixRegistrationIndex].Add(
                augend, addend, addendFactor);
        }

        /// <summary>
        ///     Adds the second matrix multiplied by the second factor to the first matrix multiplied by the first factor.
        /// </summary>
        /// <param name="augend">
        ///     The augend matrix.
        /// </param>
        /// <param name="augendFactor">
        ///     The factor of the augend matrix.
        /// </param>
        /// <param name="addend">
        ///     The addend matrix.
        /// </param>
        /// <param name="addendFactor">
        ///     The factor of the addend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="augend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="addend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Add(
            this Matrix<DoubleComplex> augend,
            DoubleComplex augendFactor,
            Matrix<DoubleComplex> addend,
            DoubleComplex addendFactor)
        {
#if SAFE

            if (augend == null)
            {
                throw new ArgumentNullException("augend");
            }

            if (addend == null)
            {
                throw new ArgumentNullException("addend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= augend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex].Length <= addend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[augend.MatrixRegistrationIndex][addend.MatrixRegistrationIndex].Add(
                augend, augendFactor, addend, addendFactor);
        }

        /// <summary>
        ///     Multiplies all elements of the matrix by the specified factor.
        /// </summary>
        /// <param name="matrix">
        ///     The matrix whose elements should be multiplied.
        /// </param>
        /// <param name="factor">
        ///     The factor to multiply.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Multiply(this Matrix<DoubleComplex> matrix, DoubleComplex factor)
        {
#if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

            if ((MatrixFactorRingOperationsProviders.Length <= matrix.MatrixRegistrationIndex) ||
                (MatrixFactorRingOperationsProviders[matrix.MatrixRegistrationIndex] == null))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixFactorRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixFactorRingOperationsProviders[matrix.MatrixRegistrationIndex].Multiply(matrix, factor);
        }

        /// <summary>
        ///     Multiplies the matrix by the dense vector and stores the result in the another vector.
        /// </summary>
        /// <param name="multiplicand">
        ///     The multiplicand matrix.
        /// </param>
        /// <param name="multiplier">
        ///     The multiplier vector.
        /// </param>
        /// <param name="result">
        ///     The vector to store the result.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="multiplicand"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="multiplier"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="result"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        /// <remarks>
        ///     The number of rows of <paramref name="multiplicand"/> to multiply is determined as
        ///     min(<paramref name="multiplicand"/>.RowsCount, <paramref name="result"/>.Length).
        ///     The number of columns of <paramref name="multiplicand"/> to multiply is determined as
        ///     min(<paramref name="multiplicand"/>.ColumnsCount, <paramref name="multiplier"/>.Length).
        /// </remarks>
        public static void Multiply(
            this Matrix<DoubleComplex> multiplicand, DenseVector<DoubleComplex> multiplier, DenseVector<DoubleComplex> result)
        {
#if SAFE

            if (multiplicand == null)
            {
                throw new ArgumentNullException("multiplicand");
            }

            if ((MultiplicationOperationsProviders.Length <= multiplicand.MatrixRegistrationIndex) ||
                (MultiplicationOperationsProviders[multiplicand.MatrixRegistrationIndex] == null))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMultiplicableMatrixOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MultiplicationOperationsProviders[multiplicand.MatrixRegistrationIndex].Multiply(multiplicand, multiplier, result);
        }

        /// <summary>
        ///     Subtracts the second matrix from the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Subtract(this Matrix<DoubleComplex> minuend, Matrix<DoubleComplex> subtrahend)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= minuend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex].Length <=
                subtrahend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex][subtrahend.MatrixRegistrationIndex].Subtract(
                minuend, subtrahend);
        }

        /// <summary>
        ///     Subtracts the second matrix from the first matrix multiplied by the factor.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="minuendFactor">
        ///     The factor of the minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Subtract(
            this Matrix<DoubleComplex> minuend, DoubleComplex minuendFactor, Matrix<DoubleComplex> subtrahend)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= minuend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex].Length <=
                subtrahend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex][subtrahend.MatrixRegistrationIndex].Subtract(
                minuend, minuendFactor, subtrahend);
        }

        /// <summary>
        ///     Subtracts the second matrix multiplied by the factor from the first matrix.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <param name="subtrahendFactor">
        ///     The factor of the subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Subtract(
            this Matrix<DoubleComplex> minuend, Matrix<DoubleComplex> subtrahend, DoubleComplex subtrahendFactor)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= minuend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex].Length <=
                subtrahend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex][subtrahend.MatrixRegistrationIndex].Subtract(
                minuend, subtrahend, subtrahendFactor);
        }

        /// <summary>
        ///     Subtracts the second matrix multiplied by the second factor from the first matrix multiplied by the first factor.
        /// </summary>
        /// <param name="minuend">
        ///     The minuend matrix.
        /// </param>
        /// <param name="minuendFactor">
        ///     The factor of the minuend matrix.
        /// </param>
        /// <param name="subtrahend">
        ///     The subtrahend matrix.
        /// </param>
        /// <param name="subtrahendFactor">
        ///     The factor of the subtrahend matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="minuend"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="subtrahend"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static void Subtract(
            this Matrix<DoubleComplex> minuend,
            DoubleComplex minuendFactor,
            Matrix<DoubleComplex> subtrahend,
            DoubleComplex subtrahendFactor)
        {
#if SAFE

            if (minuend == null)
            {
                throw new ArgumentNullException("minuend");
            }

            if (subtrahend == null)
            {
                throw new ArgumentNullException("subtrahend");
            }

            if ((MatrixMatrixRingOperationsProviders.Length <= minuend.MatrixRegistrationIndex) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex] == null) ||
                (MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex].Length <=
                subtrahend.MatrixRegistrationIndex))
            {
                throw new NotFoundOperationsProviderException(
                    typeof(IMatrixMatrixRingOperationsProvider<DoubleComplex>), typeof(MatrixExtensions));
            }

#endif

            MatrixMatrixRingOperationsProviders[minuend.MatrixRegistrationIndex][subtrahend.MatrixRegistrationIndex].Subtract(
                minuend, minuendFactor, subtrahend, subtrahendFactor);
        }

        #endregion

        #region private

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="matrixType">
        ///     The type of matrix.
        /// </param>
        /// <param name="provider">
        ///     The provider of multiplication operation.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(
            Type matrixType, IMultiplicableMatrixOperationsProvider<DoubleComplex> provider)
        {
#if SAFE

            if (matrixType == null)
            {
                throw new ArgumentNullException("matrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

#endif

            var index = Global.GetMatrixRegistrationIndex(matrixType);
            if (MultiplicationOperationsProviders.Length <= index)
            {
                Array.Resize(ref MultiplicationOperationsProviders, index + 1);
            }

            MultiplicationOperationsProviders[index] = provider;
        }

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="matrixType">
        ///     The type of matrix.
        /// </param>
        /// <param name="provider">
        ///     The provider of ring operations with factor.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(
            Type matrixType, IMatrixFactorRingOperationsProvider<DoubleComplex> provider)
        {
#if SAFE

            if (matrixType == null)
            {
                throw new ArgumentNullException("matrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

#endif

            var index = Global.GetMatrixRegistrationIndex(matrixType);
            if (MatrixFactorRingOperationsProviders.Length <= index)
            {
                Array.Resize(ref MatrixFactorRingOperationsProviders, index + 1);
            }

            MatrixFactorRingOperationsProviders[index] = provider;
        }

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="baseMatrixType">
        ///     The type of matrix that is used as object for operation.
        /// </param>
        /// <param name="parameterMatrixType">
        ///     The type of matrix that is used as parameter for operation.
        /// </param>
        /// <param name="provider">
        ///     The provider of ring operations with matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="baseMatrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="parameterMatrixType"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(
            Type baseMatrixType, Type parameterMatrixType, IMatrixMatrixRingOperationsProvider<DoubleComplex> provider)
        {
#if SAFE

            if (baseMatrixType == null)
            {
                throw new ArgumentNullException("baseMatrixType");
            }

            if (parameterMatrixType == null)
            {
                throw new ArgumentNullException("parameterMatrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

#endif

            var baseIndex = Global.GetMatrixRegistrationIndex(baseMatrixType);
            if (MatrixMatrixRingOperationsProviders.Length <= baseIndex)
            {
                Array.Resize(ref MatrixMatrixRingOperationsProviders, baseIndex + 1);
            }

            var parameterIndex = Global.GetMatrixRegistrationIndex(parameterMatrixType);
            if (MatrixMatrixRingOperationsProviders[baseIndex] == null)
            {
                MatrixMatrixRingOperationsProviders[baseIndex] =
                    new IMatrixMatrixRingOperationsProvider<DoubleComplex>[parameterIndex + 1];
            }
            else if (MatrixMatrixRingOperationsProviders[baseIndex].Length <= parameterIndex)
            {
                Array.Resize(ref MatrixMatrixRingOperationsProviders[baseIndex], parameterIndex + 1);
            }

            MatrixMatrixRingOperationsProviders[baseIndex][parameterIndex] = provider;
        }

        #endregion

        #endregion
    }
}